<template>
  <div class="gltf-viewer" ref="container"></div>
</template>

<script>
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

export default {
  name: 'GltfViewer',
  props: {
    // 传入模型名称，例如："scene"
    modelName: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      renderer: null,
      scene: null,
      camera: null,
      controls: null,
      animateLoop: null,
      animationId: null,
      currentModel: null
    }
  },
  computed: {
    sanitizedName() {
      const name = (this.modelName || '').trim()
      if (!name) return ''
      // 兼容常见拼写：scence -> scene
      if (name.toLowerCase() === 'scence') return 'scene'
      return name
    },
    // 统一使用相对路径，适配开发(http)与打包(file)两种协议
    modelUrl() {
      if (!this.sanitizedName) return ''
      return `model/${this.sanitizedName}.gltf`
    }
  },
  mounted() {
    this.initThree()
    this.loadModel()
    window.addEventListener('resize', this.onResize)
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.onResize)
    this.dispose()
  },
  watch: {
    modelName() {
      this.loadModel(true)
    }
  },
  methods: {
    initThree() {
      const container = this.$refs.container
      const width = container.clientWidth || container.offsetWidth || 600
      const height = container.clientHeight || container.offsetHeight || 500

      // 渲染器
      this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true })
      this.renderer.setPixelRatio(window.devicePixelRatio || 1)
      this.renderer.setSize(width, height)
      // this.renderer.setClearColor(0xeeeeee, 1)
      container.appendChild(this.renderer.domElement)

      // 场景
      this.scene = new THREE.Scene()

      // 相机
      this.camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
      this.camera.position.set(1.5, 1.5, 3)

      // 灯光
      const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.0)
      hemiLight.position.set(0, 1, 0)
      this.scene.add(hemiLight)

      const dirLight = new THREE.DirectionalLight(0xffffff, 1.0)
      dirLight.position.set(3, 5, 2)
      this.scene.add(dirLight)

      // 背景颜色
      // this.scene.background = new THREE.Color(0xffffff); // 白色的十六进制值是 0xffffff
      this.scene.background = null;

      // 控制器
      this.controls = new OrbitControls(this.camera, this.renderer.domElement)
      this.controls.enableDamping = true
      this.controls.enableZoom = false  // 禁用滚轮
      // 关键配置：固定极角，禁用上下旋转
      // 极角范围：0（俯视）~ Math.PI（仰视），默认初始值为 Math.PI/2（平视）
      const fixedPolarAngle = Math.PI / 2; // 固定为平视角度（可根据需求调整）
      this.controls.minPolarAngle = fixedPolarAngle; // 最小极角
      this.controls.maxPolarAngle = fixedPolarAngle; // 最大极角（与最小相等，固定角度）
      this.controls.dampingFactor = 0.08
      this.controls.target.set(0, 0, 0)

      // 动画循环（绑定 this）
      this.animateLoop = () => {
        this.animationId = requestAnimationFrame(this.animateLoop)
        if (this.controls) this.controls.update()
        if (this.renderer && this.scene && this.camera) {
          this.renderer.render(this.scene, this.camera)
        }
      }
      this.animateLoop()
    },
    loadModel(replace = false) {
      if (!this.modelUrl) return
      const loader = new GLTFLoader()

      if (replace && this.currentModel) {
        this.scene.remove(this.currentModel)
        this.currentModel.traverse(obj => {
          if (obj.isMesh) {
            obj.geometry && obj.geometry.dispose()
            if (obj.material && obj.material.dispose) obj.material.dispose()
          }
        })
        this.currentModel = null
      }

      loader.load(
        this.modelUrl,
        gltf => {
          const model = gltf.scene || gltf.scenes && gltf.scenes[0]
          if (!model) return
          model.rotation.y = 24 * Math.PI / 180; // 转换为弧度并赋值
          this.currentModel = model
          this.scene.add(model)

          this.focusCameraOnModel(model)
        },
        undefined,
        error => {
          // eslint-disable-next-line no-console
          console.error('GLTF 加载失败: ', error)
        }
      )
    },
    focusCameraOnModel(model) {
      const box = new THREE.Box3().setFromObject(model)
      const size = new THREE.Vector3()
      const center = new THREE.Vector3()
      box.getSize(size)
      box.getCenter(center)

      const maxDim = Math.max(size.x, size.y, size.z)
      const fitHeightDistance = maxDim / (2 * Math.atan((this.camera.fov * Math.PI) / 360))
      const fitWidthDistance = fitHeightDistance / this.camera.aspect
      const distance = Math.max(fitHeightDistance, fitWidthDistance)

      const direction = new THREE.Vector3()
        .subVectors(this.camera.position, this.controls.target)
        .normalize()
        .multiplyScalar(distance)

      this.controls.target.copy(center)
      this.camera.position.copy(center).add(direction)
      this.camera.near = distance / 100
      this.camera.far = distance * 100
      this.camera.updateProjectionMatrix()
      this.controls.update()
    },
    onResize() {
      const container = this.$refs.container
      if (!container || !this.renderer || !this.camera) return
      const width = container.clientWidth || container.offsetWidth
      const height = container.clientHeight || container.offsetHeight
      if (!width || !height) return
      this.renderer.setSize(width, height)
      this.camera.aspect = width / height
      this.camera.updateProjectionMatrix()
    },
    dispose() {
      if (this.animationId) {
        cancelAnimationFrame(this.animationId)
        this.animationId = null
      }
      if (this.controls) {
        this.controls.dispose()
        this.controls = null
      }
      if (this.currentModel) {
        this.scene.remove(this.currentModel)
        this.currentModel.traverse(obj => {
          if (obj.isMesh) {
            obj.geometry && obj.geometry.dispose()
            if (obj.material && obj.material.dispose) obj.material.dispose()
          }
        })
        this.currentModel = null
      }
      if (this.renderer) {
        this.renderer.dispose()
        if (this.renderer.domElement && this.renderer.domElement.parentNode) {
          this.renderer.domElement.parentNode.removeChild(this.renderer.domElement)
        }
        this.renderer = null
      }
      this.scene = null
      this.camera = null
    }
  }
}
</script>

<style scoped>
.gltf-viewer {
  width: 100%;
  height: 100%;
  display: block;
}
</style>